package de.jmda.core.mproc.task;

import static de.jmda.core.mproc.ProcessingUtilities.getSubtypes;
import static de.jmda.core.mproc.ProcessingUtilities.isProcessingOver;
import static de.jmda.core.mproc.ProcessingUtilities.isSubtype;
import static de.jmda.core.mproc.ProcessingUtilities.normalizeBinaryClassName;
import static de.jmda.core.mproc.ProcessingUtilities.withEnclosedTypeElements;
import static de.jmda.core.util.CollectionsUtil.asSet;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;

import org.junit.BeforeClass;
import org.junit.Test;

import de.jmda.core.mproc.task.AbstractTypeElementsTaskPackages;
import de.jmda.core.mproc.task.TaskException;
import de.jmda.core.mproc.task.TaskRunner;

public class JUTNavigateElementsInheritance
{
  private class A { }
	private class B extends A { }
	@SuppressWarnings("unused")
  private class C extends A { }
	@SuppressWarnings("unused")
  private class D extends A { }

	private static class TypeElementsTask extends AbstractTypeElementsTaskPackages
	{
		private static Set<TypeElement> typeElements;

		private static TypeElement typeelement_A;
		private static TypeElement typeelement_B;

		private static Set<TypeElement> subTypesOfA = new HashSet<>();
		private static Set<TypeElement> subTypesOfB = new HashSet<>();

		public TypeElementsTask(Set<Package> packages)
		{
			super(packages);
		}

		@Override
		public boolean execute() throws TaskException
		{
			if (false == isProcessingOver())
			{
				typeElements = withEnclosedTypeElements(getTypeElements());

				typeelement_A =
    				findTypeElement(typeElements, normalizeBinaryClassName(A.class));
				typeelement_B =
						findTypeElement(typeElements, normalizeBinaryClassName(B.class));

				TypeMirror typemirror_A = typeelement_A.asType();
				TypeMirror typemirror_B = typeelement_B.asType();
				
				for (TypeElement typeElement : typeElements)
				{
					if (isSubtype(typeElement.asType(), typemirror_A))
					{
						subTypesOfA.add(typeElement);
					}
					else if (isSubtype(typeElement.asType(), typemirror_B))
					{
						subTypesOfB.add(typeElement);
					}
				}
			}

			return false;
		}

		private TypeElement findTypeElement(
				Iterable<? extends TypeElement> typeElements, String name)
		{
			for (TypeElement typeElement : typeElements)
			{
				if (typeElement.getQualifiedName().toString().equals(name))
				{
					return typeElement;
				}
			}

			return null;
		}
	}

	@BeforeClass
	public static void beforeClass() throws IOException
	{
		TypeElementsTask task = new TypeElementsTask(asSet(A.class.getPackage()));

		TaskRunner.run(task);

		if (TypeElementsTask.typeelement_A == null)
				fail("type element for class A is null");
		if (TypeElementsTask.typeelement_B == null)
				fail("type element for class B is null");
	}

	@Test
	public void testAIsSubTypeOfB() throws IOException
	{
		assertTrue(
				B.class.getName() + " as expected sub type of " +
				A.class.getName() + " not found",
				TypeElementsTask.subTypesOfA.contains(TypeElementsTask.typeelement_B));
	}

	@Test
	public void testBIsNotSubTypeOfA() throws IOException
	{
		assertFalse(
				A.class.getName() + " as sub type of " +
				B.class.getName() + " found",
				TypeElementsTask.subTypesOfB.contains(TypeElementsTask.typeelement_A));
	}

	@Test
	public void testSubTypeOfA() throws IOException
	{
		assertEquals(
				4, // each type is a sub type of itself
				getSubtypes(
						TypeElementsTask.typeelement_A, TypeElementsTask.typeElements).size());
	}
}